<?php

namespace App\Exceptions;

use App\Helpers\LogHelper;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Throwable;

class Handler extends ExceptionHandler
{
    /**
     * A list of the exception types that are not reported.
     *
     * @var array
     */
    protected $dontReport = [
        AuthorizationException::class,
        HttpException::class,
        ModelNotFoundException::class,
        ValidationException::class,
        //
    ];

    protected $http400 = [
        NotFoundHttpException::class,
        MethodNotAllowedHttpException::class,
    ];

    /**
     * A list of the inputs that are never flashed for validation exceptions.
     *
     * @var array
     */
    protected $dontFlash = [
        'password',
        'password_confirmation',
    ];

    /**
     * Report or log an exception.
     *
     * This is a great spot to send exceptions to Sentry, Bugsnag, etc.
     *
     * @param Throwable $e
     *
     * @return void
     *
     * @throws Throwable
     */
    public function report(Throwable $e)
    {
        $this->writeApiLog($e);
        if (env('APP_LOG_OPEN', false)) {
            parent::report($e);
        }
    }

    /**
     * Render an exception into an HTTP response.
     *
     * @param Request   $request
     * @param Throwable $e
     *
     * @return Response|JsonResponse
     *
     * @throws Throwable
     */
    public function render($request, Throwable $e)
    {
        if (env("APP_DEBUG", false)) {
            return parent::render($request, $e);
        } else {
            $dealRet = $this->dealExCodeAndMsg($e);

            return outputJsonError((int)$dealRet['show_code'], $dealRet['show_msg']);
        }
    }

    /**
     * 记录日志
     *
     * @param Throwable $exception
     *
     * @return void
     */
    private function writeApiLog(Throwable $exception): void
    {
        // 异常日志
        !$this->shouldNotReport($exception) && LogHelper::exLog($exception);
    }

    /**
     * 获取展示和日志记录的错误码和信息
     *
     * @param Throwable $exception
     *
     * @return array
     */
    private function dealExCodeAndMsg(Throwable $exception): array
    {
        $showMsg  = $exception->getMessage();    // 错误信息
        $showCode = $exception->getCode();        // 错误码
        if ($this->shouldHttp404($exception)) {
            /**
             * 路由404,405处理
             *
             * @var $exception MethodNotAllowedHttpException | NotFoundHttpException
             * @tips 替换输出的错误信息，接口返回错误码
             */
            $showCode = $exception->getStatusCode();
        } else {
            if ($showCode == 0) {
                /**
                 * 默认展示的错误码和错误信息,包括语法错误（Code == 0）和非自定义的异常类型
                 *
                 * @tisp 替换输出的错误信息，接口返回错误码，错误信息
                 */
                $showMsg  = BaseException::DEFAULT_EXCEPTION_MSG;
                $showCode = BaseException::DEFAULT_EXCEPTION_CODE;
            } elseif (isSubClassOfBaseException($exception)) {
                /**
                 * 处理自定义异常类
                 *
                 * @var $exception BaseException
                 * @tips 替换输出的错误信息，接口返回错误码，错误信息
                 */
                if (!$exception->checkCustomizeCode($showCode)) {
                    $showMsg = $exception->getShowMsg($showCode);
                }
                $showCode = $exception->getFullCode($showCode);	// 业务错误码
            }
        }

        return [
            'show_code' => $showCode,
            'show_msg'  => $showMsg,
        ];
    }

    /**
     * Determine if the exception is in the "do not report" list.
     *
     * @param Throwable $e
     * @return bool
     */
    private function shouldNotReport(Throwable $e): bool
    {
        $dontReport = array_merge($this->dontReport, $this->http400);

        return $this->shouldNot($dontReport, $e);
    }

    /**
     * @param Throwable $e
     * @return bool
     */
    private function shouldHttp404(Throwable $e): bool
    {
        return $this->shouldNot($this->http400, $e);
    }

    /**
     *
     * @param array     $dontReport
     * @param Throwable $e
     * @return bool
     */
    private function shouldNot(array $dontReport, Throwable $e): bool
    {
        foreach ($dontReport as $type) {
            if ($e instanceof $type) {
                return true;
            }
        }

        return false;
    }

    /**
     * Register the exception handling callbacks for the application.
     *
     * @return void
     */
    public function register()
    {
        $this->reportable(function (Throwable $e) {
            //
        });
    }
}
